﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;

namespace Develore.GDiff
{
	/// <summary>
	/// Represents the Adler-32 hash algorithm as specified in RFC 1950. The Adler-32
	/// algorithm is also described on Wikipedia at http://en.wikipedia.org/wiki/Adler-32.
	/// </summary>
	/// <remarks>
	/// The Adler-32 algorithm is a rolling hash algoritm that is used to hash data in a
	/// smaller window that moves over the full stream of data. For details see Wikipedia
	/// at http://en.wikipedia.org/wiki/Rolling_hash.
	/// </remarks>
	public sealed class Adler32 : HashAlgorithm , IEquatable<Adler32>
	{

		#region #ctor: public Adler32()

		/// <summary>
		/// Creates a new instance of the hash algorithm.
		/// </summary>
		public Adler32()
		{
			this.HashSizeValue = 32;
		}

		#endregion
		
		#region #ctor: public Adler32(byte[] buffer)

		/// <summary>
		/// Creates a new instance of the class by hashing the data contained in
		/// the given buffer.
		/// </summary>
		/// <param name="buffer">The buffer containing the data to hash.</param>
		public Adler32(byte[] buffer) : this(buffer, 0, buffer.Length) { }
		
		#endregion

		#region #ctor: public Adler32(byte[] buffer, int offset, int count)

		/// <summary>
		/// Creates a new instance of the class and hashes the data contained in
		/// the given buffer starting from <paramref name="offset"/> and using
		/// <paramref name="count"/> number of bytes.
		/// </summary>
		/// <param name="buffer">The buffer containing the data to hash.</param>
		/// <param name="offset">The index in the buffer at which to start hashing.</param>
		/// <param name="count">The number of bytes starting from <paramref name="offset"/> to use when computing the hash.</param>
		public Adler32(byte[] buffer, int offset, int count) : this()
		{
			this.ComputeHash(buffer, offset, count);
		}

		#endregion



		#region public override byte[] Hash { get; }

		/// <summary>
		/// Returns the computed hash value.
		/// </summary>
		public override byte[] Hash
		{
			get
			{
				byte[] bytes = BitConverter.GetBytes(this.Value);
				if (BitConverter.IsLittleEndian)
					return bytes.Reverse().ToArray();

				return bytes;
			}
		}

		#endregion

		#region public uint Value { get; }

		/// <summary>
		/// Returns the hash value computed from the given data.
		/// </summary>
		public uint Value
		{
			get { return (uint)(this.B << 16 | this.A); }
		}

		#endregion



		private int A = 1;
		private int B = 0;
		private const int AdlerBase = 0xfff1;
		private int BufferSize;



		#region public void ComputeNext(byte b0, byte bn)

		/// <summary>
		/// Computes the next hash value for the current hash value using the given two bytes as input.
		/// </summary>
		/// <remarks>
		/// This method assumes that the current hash value was calculated from a buffer
		/// <c>b(0)</c> to <c>b(n-1)</c>, so the byte given in <paramref name="b0"/> is the
		/// first byte that was used when calculating the current hash, and <paramref name="bn"/>
		/// is the next byte in the data stream that comes after the last byte that was used
		/// to compute the current hash.
		/// </remarks>
		/// <param name="b0">The first byte that was used to compute the current hash.</param>
		/// <param name="bn">The next byte after the last byte that was used to compute the current hash.</param>
		public void ComputeNext(byte b0, byte bn)
		{
			int i = -621;
			i %= AdlerBase;

			this.A -= b0;
			while (this.A < 0) this.A += AdlerBase;
			this.A %= AdlerBase;

			this.B -= (b0 * this.BufferSize);
			while (this.B < 0) this.B += AdlerBase;
			this.B--;
			while (this.B < 0) this.B += AdlerBase;
			this.B %= AdlerBase;

			this.A += bn;
			this.A %= AdlerBase;

			this.B += this.A;
			this.B %= AdlerBase;
		}

		#endregion

		#region public override bool Equals(object obj)

		/// <summary>
		/// Compares the given object to the current hash.
		/// </summary>
		/// <param name="obj">The object to compare to the current.</param>
		/// <returns>Returns true if the given object equals the current.</returns>
		public override bool Equals(object obj)
		{
			if (obj is Adler32)
				return this.Equals((Adler32)obj);

			return false;
		}

		#endregion

		#region public bool Equals(Adler32 other)

		/// <summary>
		/// Compares the given object to the current hash.
		/// </summary>
		/// <param name="other">The other hash object to compare to the current.</param>
		/// <returns>Returns true if the given object equals the current.</returns>
		public bool Equals(Adler32 other)
		{
			if (null == other)
				return false;

			return this.Value.Equals(other.Value);
		}

		#endregion

		#region public override int GetHashCode()

		/// <summary>
		/// Returns the hash code for the current hash object.
		/// </summary>
		public override int GetHashCode()
		{
			return this.Value.GetHashCode();
		}

		#endregion

		#region public override void Initialize()

		/// <summary>
		/// Not implemented.
		/// </summary>
		public override void Initialize()
		{
		}

		#endregion
		
		#region public override string ToString()

		/// <summary>
		/// Returns the string representation of the current hash object.
		/// </summary>
		public override string ToString()
		{
			byte[] buffer = BitConverter.GetBytes(this.Value);
			if (BitConverter.IsLittleEndian)
				buffer = buffer.Reverse().ToArray();

			return BitConverter.ToString(buffer);
		}

		#endregion



		#region protected override void HashCore(byte[] array, int ibStart, int cbSize)

		/// <summary>
		/// Computes the hash value for the given buffer starting from <paramref name="ibStart"/>
		/// using the number of bytes specified in <paramref name="cbSize"/>.
		/// </summary>
		/// <param name="array">The data to hash.</param>
		/// <param name="ibStart">The starting index in the given <paramref name="array"/>.</param>
		/// <param name="cbSize">The number of bytes to use from <paramref name="array"/> to compute the hash value.</param>
		protected override void HashCore(byte[] array, int ibStart, int cbSize)
		{
			this.ComputeAdler32(array, ibStart, cbSize, out this.A, out this.B);
			this.BufferSize = cbSize;
		}

		#endregion

		#region protected override byte[] HashFinal()

		/// <summary>
		/// Finalizes the hash computation.
		/// </summary>
		protected override byte[] HashFinal()
		{
			return this.Hash;
		}

		#endregion



		private void ComputeAdler32(byte[] buffer, int offset, int count, out int a, out int b)
		{

			if (null == buffer)
				throw new ArgumentNullException("buffer");

			if (buffer.Length < 1)
				throw new ArgumentException("The given buffer is empty.", "buffer");

			if (offset >= buffer.Length)
				throw new IndexOutOfRangeException("Offset is out of range.");

			if (buffer.Length + offset < count)
				throw new ArgumentException("Count is too large.", "count");


			a = 1;
			b = 0;

			for (int i = offset; i < count + offset; i++)
			{
				a += buffer[i];
				a %= AdlerBase;

				b += a;
				b %= AdlerBase;
			}

		}

	}
}
